package wsi.script;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.rygh.core.db.Database;
import org.rygh.core.db.impl.Derby;
import org.rygh.core.html.element.HtmlUl;
import org.rygh.core.model.common.MapList;
import org.rygh.core.util.UTime;
import org.rygh.core.xml.XmlLeaf;
import org.rygh.core.xml.XmlObject;
import org.rygh.core.xml.XmlPath;
import org.rygh.semantic.ConfigSemantic;
import org.rygh.semantic.model.rdf.Entity;
import org.rygh.semantic.model.rdf.Predicate;
import org.rygh.semantic.sparql.SparQL;

import wsi.exp.Exp2TreeToEntity;
import wsi.function.Function;
import wsi.function.Function.Phrase;
import wsi.function.Function.PhraseEx;
import wsi.function.FunctionController;
import wsi.function.RuntimeInfo;
import wsi.match.MatchProperty;
import wsi.task.Engine.InputSelectType;
import wsi.ws.WebServices;

public class AppConfig extends AppXml {

	private static AppConfig shared = null;

	public static void init() {
		init(DEFAULT_FILE);
	}

	public static void init(String filePath) {
		if (filePath == null)
			filePath = DEFAULT_FILE;

		if (shared == null) {
			shared = new AppConfig(filePath);

			if (shared.hasTree()) {
				Derby.init(shared.getRelationDatabase(),
						shared.getCacheFolder());

				ConfigSemantic.config(shared.getKnowledgeDatabase(),
						shared.getDomain(), shared.getJenaIndexPath(),
						shared.getResourcesFolder(), shared.getDegreeLimit());

				shared.getExperimentsFolder().mkdir();
				shared.getInputsFolder().mkdir();
				shared.getFeedbackInputsFolder().mkdir();
				shared.getCacheFolder().mkdir();

				// after set domain.

				// YagoTypeTree.init();
				shared.loadQueries();
				shared.check();

				FunctionController.shared().loadByInputType();
			}
		}
	}

	public static void reset() {
		shared = null;
	}

	public static AppConfig shared() {
		assert shared != null;
		return shared;
	}

	public static void shutdown() {
		Database.shared().shutdown();
	}

	private final boolean webCacheOn;

	private final int inputSize;
	private final int degreeLimit;
	private final int hop;

	private final double functionalityThreshold;
	private final double resolutionThreshold;

	private final InputSelectType inputSelectType;
	private final File runsFolder;

	private final File goldSetFolder;
	private final File resourcesFolder;
	private final File cacheFolder;
	private final File databaseFolder;
	private final String kdb;
	private final String rdb;
	private final String jena;
	private final String domain;

	public AppConfig(String filePath) {
		super(filePath);

		if (hasTree()) {
			// be first to set db
			rdb = super.getRelationDatabase();
			kdb = super.getKnowledgeDatabase();
			jena = super.getJena();

			webCacheOn = super.isWebCacheOn();
			inputSize = super.getInputSize();
			degreeLimit = super.getDegreeLimit();
			hop = super.getHop();
			functionalityThreshold = super.getFunctionalityThreshold();
			resolutionThreshold = super.getResolutionThreshold();

			runsFolder = super.getRunsFolder();
			goldSetFolder = super.getGoldSetFolder();
			resourcesFolder = super.getResourcesFolder();
			cacheFolder = super.getCacheFolder();
			databaseFolder = super.getDatabaseFolder();
			domain = super.getDomain();
			inputSelectType = super.getInputSelectType();
		} else {
			webCacheOn = false;
			inputSize = 0;
			degreeLimit = 0;
			hop = 0;
			functionalityThreshold = 0;
			resolutionThreshold = 0;

			runsFolder = null;
			goldSetFolder = null;
			resourcesFolder = null;
			cacheFolder = null;
			databaseFolder = null;
			rdb = null;
			kdb = null;
			domain = null;
			jena = null;
			inputSelectType = null;
		}
	}

	protected void check() {
		check(false);
	}

	public void check(boolean force) {
		if (force)
			assert checkQueries();

		assert getRunsFolder().exists() : "runs folder expected: "
				+ getRunsFolder().getAbsolutePath();
		assert getFunctionsFolder().exists() : "function folder expected: "
				+ getFunctionsFolder().getAbsolutePath();

		assert getGoldSetFolder().exists() : "gold-set folder expected.";
		assert getResourcesFolder().exists() : "res folder expected.";

		assert getCacheFolder().exists() : "cache folder expected.";

		assert getDatabaseFolder().exists() : "db folder expected.";
		assert new File(getJenaIndexPath()).exists() : "jena index folder expected.";

		assert getInputSize() > 0 && getInputSize() < 1000;
		assert getFunctionalityThreshold() > 0
				&& getFunctionalityThreshold() < 1;

		FunctionController fctrl = FunctionController.shared();
		Function function;

		List<XmlLeaf> services = tree.getNodesByXPath(XmlPath.compose(
				AppTag.application, AppTag.workflow, AppTag.web_service));

		Exp2TreeToEntity exp2 = new Exp2TreeToEntity();
		exp2.load2();
		MapList<Function, MatchProperty> goldMap2 = exp2.getGoldSetMap2();
		MapList<Function, MatchProperty> autoMap2 = exp2.getAutomaticMap2();
		Set<String> func_name_set = new HashSet<>();

		for (XmlLeaf service_node : services) {
			XmlObject obj = service_node.getValue();

			if (!force
					&& !Boolean
							.parseBoolean(obj.getAttribute(AppTag.switch_on)))
				continue;

			String service = obj.getAttribute(AppTag.name);
			func_name_set.clear();

			for (XmlLeaf f_node : service_node.getChildren()) {
				String func_name = f_node.getValue().getAttribute(AppTag.name);

				assert !func_name_set.contains(func_name) : func_name;
				func_name_set.add(func_name);

				if (!force
						&& !Boolean.parseBoolean(f_node.getValue()
								.getAttribute(AppTag.switch_on)))
					continue;

				function = fctrl.getFunction(service, func_name);
				assert function != null : String.format("%s/%s", service,
						func_name);

				if (f_node.isLeaf()) {
					assert function.isDirect() : function.getFullName();

					String query_id = f_node.getValue().getAttribute(
							AppTag.input_query);

					assert queryMap.containsKey(query_id) : String.format(
							"[error] missing query %s for function %s",
							query_id, function.getFullName());
					assert queryMap.get(query_id).getNamePredicate() != null : String
							.format("[error] missing name pred for %s",
									query_id);

				} else {
					assert !function.isDirect() : function.getFullName();

					XmlLeaf node = f_node.getFirstChild();
					XmlLeaf depend_func_node = node.getFirstChild();
					String depend = depend_func_node.getValue().getAttribute(
							AppTag.name);

					Function depend_func = fctrl.getFunction(service, depend);
					assert depend_func != null : String.format("%s/%s on %s",
							service, func_name, depend);

					// check match property

					MatchProperty property = MatchProperty
							.getMatchProperty(depend_func_node);

					assert property != null : depend_func_node.getValue()
							.getAttribute(AppTag.name);

					// make sure what in script is consistent with gold

					List<MatchProperty> golds = goldMap2.get(depend_func);

					if (golds == null)
						System.out.printf("gold does not contain %s\n",
								depend_func.getFullName());
					else {
						assert (property.equalsToAny(golds)) : String
								.format("function %s on %s wrong gold match %s expect gold %s",
										function.getFullName(),
										depend_func.getFullName(),
										property.toString(), golds.toString());
					}

					// check if auto is valid (same with gold)

					List<MatchProperty> autos = autoMap2.get(depend_func);

					if (autos == null) {
						// System.out.printf("auto does not contain %s\n",
						// depend_func.getFullName());
					} else {
						if (golds != null
								&& !MatchProperty.equalsToAny(golds, autos))
							System.out
									.printf("function %s on %s wrong auto match %s expect gold %s\n",
											function.getFullName(),
											depend_func.getFullName(),
											autos.toString(), golds.toString());
					}

					// assert function.isDirect() : function.getFullName();
				}
			}
		}
	}

	@Override
	public File getCacheFolder() {
		return cacheFolder;
	}

	public RuntimeInfo getConfig(Function function) {
		List<XmlLeaf> ws_nodes = tree.getNodesByXPath(XmlPath.compose(
				AppTag.application, AppTag.workflow, AppTag.web_service));

		final String service = function.getService();
		final String name = function.getName();
		XmlLeaf config_node = null;

		for (XmlLeaf ws_node : ws_nodes) {
			XmlObject obj = ws_node.getValue();

			if (!obj.getAttribute(AppTag.name).equals(service))
				continue;

			List<XmlLeaf> function_nodes = ws_node.getChildren();

			for (XmlLeaf f_node : function_nodes) {
				obj = f_node.getValue();

				if (!obj.getAttribute(AppTag.name).equals(name))
					continue;

				config_node = f_node;
				break;
			}

			if (config_node != null)
				break;
		}

		if (config_node == null)
			return null;

		return new RuntimeInfo(function, config_node);
	}

	public HtmlUl getConfigHtml() {
		HtmlUl ul = new HtmlUl();
		ul.addElement("<b>Date:</b> " + UTime.toTodayString());
		ul.addElement("<b>Database:</b> " + getKnowledgeDatabase());
		ul.addElement("<b>Web Cache:</b> " + isWebCacheOn());
		ul.addElement("<b>Input Size:</b> " + getInputSize());
		ul.addElement("<b>Input Select:</b> " + getInputSelectType().name());
		ul.addElement("<b>Resolve Thresold:</b> " + getResolutionThreshold());
		ul.addElement("<b>Functionality Thresold:</b> "
				+ getFunctionalityThreshold());
		ul.addElement("<b>Output:</b> " + getRunsFolder().getAbsolutePath());

		return ul;
	}

	@Override
	public int getDegreeLimit() {
		return degreeLimit;
	}

	public int getDegreeMax(String key) {
		return queryMap.get(key).getMaxDegree();
	}

	@Override
	public String getDomain() {
		return domain;
	}

	public File getExperimentsFolder() {
		return new File(runsFolder, "experiments");
	}

	public File getFeedbackInputsFolder() {
		return new File(getResourceInputFolder(), "feedback");
	}

	@Override
	public double getFunctionalityThreshold() {
		return functionalityThreshold;
	}

	public File getFunctionsFolder() {
		return new File(runsFolder, "functions");
	}

	@Override
	public File getGoldSetFolder() {
		return new File(goldSetFolder, kdb);
	}

	public File getGoldSetFolder(String kdb) {
		return new File(goldSetFolder, kdb);
	}

	@Override
	public int getHop() {
		return hop;
	}

	@Override
	public InputSelectType getInputSelectType() {
		return inputSelectType;
	}

	public File getInputsFolder() {
		return new File(runsFolder, "inputs");
	}

	@Override
	public int getInputSize() {
		return inputSize;
	}

	public String getJenaIndexPath() {
		return new File(new File(databaseFolder, jena), "jena_index")
				.getAbsolutePath();
	}

	@Override
	public String getKnowledgeDatabase() {
		return kdb;
	}

	public Predicate getLinkPredicate(String key) {
		return queryMap.get(key).getLinkPredicate();
	}

	public Predicate getNamePredicate(String key) {
		return queryMap.get(key).getNamePredicate();
	}

	public List<XmlLeaf> getOnFunctions(XmlLeaf serviceNode) {
		List<XmlLeaf> onList = new ArrayList<>();

		for (XmlLeaf node : serviceNode.getChildren()) {
			XmlObject obj = node.getValue();

			boolean on = Boolean.parseBoolean(obj
					.getAttribute(AppTag.switch_on));

			if (on)
				onList.add(node);
		}

		return onList;
	}

	public List<XmlLeaf> getOnServices() {
		return getOnServices(null);
	}

	public List<XmlLeaf> getOnServices(WebServices ws) {
		List<XmlLeaf> nodes = tree.getNodesByXPath(XmlPath.compose(
				AppTag.application, AppTag.workflow, AppTag.web_service));
		List<XmlLeaf> onList = new ArrayList<>();

		for (XmlLeaf node : nodes) {
			XmlObject obj = node.getValue();

			boolean on = Boolean.parseBoolean(obj
					.getAttribute(AppTag.switch_on));

			if (!on)
				continue;

			if (ws != null)
				if (!ws.name().equals(obj.getAttribute(AppTag.name)))
					continue;

			onList.add(node);
		}

		return onList;
	}

	public SparQL getQuery(String key) {
		return queryMap.get(key).getQuery();
	}

	public QueryTuple getQueryTuple(String key) {
		return queryMap.get(key);
	}

	@Override
	public String getRelationDatabase() {
		return rdb;
	}

	@Override
	public double getResolutionThreshold() {
		return resolutionThreshold;
	}

	public File getResourceInputFolder() {
		return new File(getResourcesFolder(), "input");
	}

	@Override
	public File getResourcesFolder() {
		return new File(resourcesFolder, kdb);
	}

	@Override
	public File getRunsFolder() {
		return runsFolder;
	}

	public List<Phrase> getSkippedPhrases() {
		List<Phrase> skipped = new ArrayList<>();

		for (Phrase p : Phrase.values())
			skipped.add(p);

		List<XmlLeaf> nodes = tree.getNodesByXPath(XmlPath.compose(
				AppTag.application, AppTag.configuration, AppTag.parameters,
				AppTag.phrases, AppTag.phrase));

		for (XmlLeaf node : nodes) {
			XmlObject obj = node.getValue();
			String name = obj.getValue();

			if (!Boolean.parseBoolean(obj.getAttribute(AppTag.switch_on)))
				continue;

			if (name.equals(PhraseEx.SAMPLING.name())) {
				skipped.remove(Phrase.INPUT);
			} else if (name.equals(PhraseEx.SELECTED_TREES.name())) {
				skipped.remove(Phrase.YAGO_HOP);
				skipped.remove(Phrase.FUNC_XPATH);
			} else
				skipped.remove(Phrase.valueOf(name));
		}

		return skipped;
	}

	public Entity getType(String key) {
		return queryMap.get(key).getType();
	}

	public boolean isMbz() {
		return getKnowledgeDatabase().equals(MBZ);
	}

	@Override
	public boolean isWebCacheOn() {
		return webCacheOn;
	}

	public boolean isYago() {
		return getKnowledgeDatabase().equals(YAGO);
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();

		sb.append("====================\n");
		sb.append(String.format("KDB    \t: %s\n", kdb));
		sb.append(String.format("RDB    \t: %s\n", rdb));
		sb.append(String.format("HOP    \t: %s\n", hop));
		sb.append(String.format("RUNS   \t: %s\n", runsFolder.getName()));
		sb.append(String.format("JENA   \t: %s\n", jena));
		sb.append(String.format("CACHE  \t: %s\n", webCacheOn));
		sb.append(String.format("TREE   \t: %s\n",
				AppParameter.SELECTED_TREES_ON));
		sb.append(String.format("RESV   \t: %s\n", AppParameter.RESOLVE_ON));
		sb.append(String.format("DEGREE \t: %s\n", degreeLimit));
		sb.append(String.format("INPUT  \t: %s\n", inputSize));
		sb.append(String.format("SELECT \t: %s\n", inputSelectType.name()));
		sb.append("====================\n");

		return sb.toString();
	}

}
